require "set"
require "ostruct"

module Wondercap

  module InnerConfigurationMixin
    def self.included(base)
      base.send :alias_method, :task_without_inner_configuration_mixin, :task
      base.send :alias_method, :task, :task_with_inner_configuration_mixin
    end
    
    def connection_factory
      outer_configuration.connection_factory
    end
    private :connection_factory
    
    def act_as(conf_name, options = {}, &block)
      outer_configuration.make_acting_as(self, conf_name, options)
      instance_eval(&block) if block
      self
    end
    
    def use(name_hash, &block)
      raise "name hash with single value expected" unless name_hash.respond_to? :each_pair
      raise "extension's name hash should have only one value" unless name_hash.size == 1

      extend_substrategy(name_hash.keys.first, name_hash.values.first, block)
      self
    end

    def extensions_info
      @extensions_info ||= {}
    end
    
    def extension_contexts
      @extension_contexts ||= []
    end
    attr :current_extension_context, true
    attr :current_unmangled_task, true
    
    class ExtensionContext
      attr_reader :substrategy, :extension, :tasks
      attr_accessor :configuration_block, :index
      
      def initialize(substrategy, extension, configuration_block)
        @substrategy, @extension = substrategy.to_s, extension.to_s
        @configuration_block = configuration_block
        @tasks = {}
      end
    end
      
    def extend_substrategy(substrategy, extension, configuration_block = nil)
      (extensions_info[substrategy] ||= []) << extension

      ext_block, ext_options = get_substrategy_extension(substrategy, extension)
      if current_extension_context && current_extension_context.configuration_block
        cur_block = current_extension_context.configuration_block
        wrapped_configuration_block = lambda do
          instance_eval(&cur_block)
          instance_eval(&configuration_block) if configuration_block
        end
      else
        wrapped_configuration_block = configuration_block
      end
      context = ExtensionContext.new(substrategy, extension, wrapped_configuration_block)
      
      prev_context = current_extension_context
      self.current_extension_context = context
      instance_eval(&ext_block)
      self.current_extension_context = prev_context
      
      context.index = extension_contexts.length
      extension_contexts << context
    end
    
    def get_substrategy_extension(substrategy, extension)
      outer_configuration.get_substrategy_extension(substrategy, extension)
    end
    private :get_substrategy_extension
    
    def wrap_block_with_context(block, context)
      conf = self
      lambda do
        prev_context = conf.current_extension_context
        conf.current_extension_context = context
        
        if context.configuration_block
          instance_eval(&context.configuration_block)
          context.configuration_block = nil
        end
        result = instance_eval(&block)
        
        conf.current_extension_context = prev_context
        result
      end
    end
    private :wrap_block_with_context
    
    @@mangle_counter = 0
    def task_with_inner_configuration_mixin(name, options = {}, &block)
      unless options.has_key?(:roles) || options.has_key?(:only) || options.has_key?(:except)
        options = options.merge({ :roles => :default })
      end
      
      if current_extension_context
        mangled_name = current_extension_context.tasks[name.to_sym]
        unless mangled_name
          mangled_name = "#{name.to_s}_#{current_extension_context.substrategy}_#{current_extension_context.extension}_#{@@mangle_counter}"
          @@mangle_counter = @@mangle_counter + 1
        end
        
        task_without_inner_configuration_mixin(mangled_name, options, &wrap_block_with_context(block, current_extension_context))
        current_extension_context.tasks[name.to_sym] = mangled_name.to_sym
        
        unless find_task(name)
          
          task_without_inner_configuration_mixin name do
            prev_current_unmangled_task = self.current_unmangled_task
            self.current_unmangled_task = name
            begin
              extension_contexts.reverse_each do |ctx|
                mangled_name = ctx.tasks[name.to_sym]
                if mangled_name
                  find_and_execute_task(mangled_name)
                  break
                end
              end

              raise NameError, "no task #{name} found" unless mangled_name
            ensure
              self.current_unmangled_task = prev_current_unmangled_task
            end
          end

        end
      else
        task_without_inner_configuration_mixin(name, options, &block)
      end
    end
    
   
    def execute_previous
      raise "no previous context available" unless current_extension_context
      
      extension_contexts[0...current_extension_context.index].reverse_each do |ctx|
        mangled_name = ctx.tasks[current_unmangled_task]
        if mangled_name
          return find_and_execute_task(mangled_name)
        end
      end
      raise "no previous task for #{current_unmangled_task} found"
    end
    
    def previous_defined?
      raise "no previous context available" unless current_extension_context
      extension_contexts[0...current_extension_context.index].reverse_each do |ctx|
        return true if ctx.tasks[current_unmangled_task]
      end
      false
    end

  end

end

